Skip to content

specs(exact): propose TON exact scheme for x402 v2 (spec-only)#1455

Open
ohld wants to merge 19 commits intox402-foundation:mainfrom
ohld:feat/scheme-exact-ton
Open

specs(exact): propose TON exact scheme for x402 v2 (spec-only)#1455
ohld wants to merge 19 commits intox402-foundation:mainfrom
ohld:feat/scheme-exact-ton

Conversation

@ohld
Copy link
Copy Markdown

@ohld ohld commented Mar 5, 2026

Description

Adds formal specification for the exact payment scheme on TON blockchain, following the same structure as existing network-specific scheme documents (EVM, SVM, Stellar, Aptos).

  • Adds specs/schemes/exact/scheme_exact_ton.md -- full spec for TON exact scheme
  • Updates specs/schemes/exact/scheme_exact.md -- adds TON validation rules to the index
  • No SDK/runtime implementation changes
  • Implementation PR follows after spec approval

Tests

No code affected.

Checklist

  • My commits are signed (required for merge)

Why TON

  • 950M+ Telegram users with built-in TON wallets
  • USDT on TON: 60B+ total transfer volume
  • Sub-second finality, <0.01 USD transaction fees
  • W5 wallet standard enables native gasless transactions -- architecturally equivalent to EIP-3009
  • Natural fit for AI agent payments in Telegram ecosystem

Working proof

Key design decisions

  1. W5+ wallet internal_signed -- TON equivalent of EIP-3009. Client signs, relay submits. Facilitator cannot modify destination or amount.
  2. Gasless relay sponsors gas -- client never pays TON for fees, only the token amount. Non-sponsored flow also documented.
  3. Relay-agnostic design -- TONAPI is a reference implementation, not a requirement. Any entity can act as relay.
  4. TEP-74 Jetton transfers only -- stablecoins first (USDT), expandable to any TEP-74 token.
  5. CAIP-2 identifiers -- uses official TVM namespace: tvm:-239 (mainnet), tvm:-3 (testnet) per https://namespaces.chainagnostic.org/tvm/caip2

v2 alignment

  • Uses v2 headers: PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE
  • Uses x402Version: 2 and PaymentRequirements.amount
  • Includes explicit facilitator safety/relay sponsorship rules
  • /settle performs full verification independently (does not trust prior /verify)
  • Full JSON examples matching x402 style

Review request

Please review the scheme design, payload shape, and verification/settlement safety constraints. Once aligned, I will open a separate implementation PR in TypeScript.

@cb-heimdall
Copy link
Copy Markdown

cb-heimdall commented Mar 5, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 5, 2026

@ohld is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added the specs Spec changes or additions label Mar 5, 2026
@ohld ohld force-pushed the feat/scheme-exact-ton branch from fbf520a to 86c1427 Compare March 5, 2026 07:40
@arjun215-eng
Copy link
Copy Markdown

arjun215-eng commented Mar 5, 2026

Really excited to see TON getting a proper x402 spec — the W5/internal_signed <=> EIP-3009 mapping is elegant. A few things I noticed that might be worth addressing before the implementation PR:

1. >= vs == and the exact scheme semantics
scheme_exact.md requires amount MUST equal requirements.amount exactly, but the TON addition uses >= in both the index and the spec body (verification rules §1 and §3). I'm guessing this is because the relay commission is bundled into the same jetton_transfer, pushing the gross transfer slightly above requirements.amount — but if so, it probably needs to be called out explicitly, and some bound on overpayment defined. Otherwise >= requirements.amount could pass for any amount, which feels at odds with the scheme name.

2. stateInit (seqno == 0) — worth adding a code hash check
Step 7 mentions including stateInit for new wallet deployments, but there's no rule telling facilitators to verify the deployed contract is actually a W5 wallet. A malicious client could potentially submit a stateInit for an arbitrary contract. Might be worth adding: facilitator MUST verify the code hash matches the canonical W5 v5r1 hash before accepting a seqno-0 payload.

3. Relay commission amount is undefined
Verification rule 4 says the W5 message "MUST NOT contain additional unrelated actions beyond the payment transfer and relay commission" — but the commission amount isn't communicated anywhere in PaymentRequirements or PaymentPayload. Without it, how does a facilitator distinguish a valid commission from an inflated one? Maybe extra.relayCommission in PaymentRequirements, or a specified max ratio?

@ohld
Copy link
Copy Markdown
Author

ohld commented Mar 6, 2026

Thanks for the thorough review, these are all great catches.

1. >=== (exact amount)

You're right — >= was wrong here. The relay commission on TON is a separate jetton_transfer action in the W5 batch, not bundled into the payment transfer amount. So the payment to the recipient is exactly requirements.amount, and the commission goes to extra.relayAddress independently. Fixed both the spec and the index to use strict equality, matching SVM/Stellar/EVM.

2. stateInit code hash check

Good call. I didn't want to hardcode a specific hash in the spec since W5 has revisions (v5r1, potentially v5r2) and a hardcoded hash would become outdated when TON Foundation ships a new version. Instead I added: facilitator MUST verify that the contract code in stateInit matches a known W5 wallet contract, and SHOULD maintain an allowlist of accepted wallet code hashes. This follows how SVM handles program IDs — the validation logic is in the facilitator implementation, not frozen in the spec.

3. Relay commission bounds

Added extra.maxRelayCommission (optional) to PaymentRequirements. When the resource server sets it, the facilitator rejects W5 batches where the commission transfer exceeds that cap. When absent, the facilitator applies its own policy. This gives resource servers explicit control without hardcoding a ratio that would break as gas prices change.

Also tightened rule §4: the W5 message must contain exactly two actions (payment transfer + optional commission transfer), anything else is rejected.

All changes in the latest commit + updated the demo repo to enforce exact amount matching (30/30 tests passing).

@phdargen
Copy link
Copy Markdown
Collaborator

Thanks a lot for putting this together @ohld!

Notified the core team to review. Please be aware that we currently have a lot of pending/incoming requests, so it might take a bit until we can get back to you, thanks for your patience 🙏

@ohld
Copy link
Copy Markdown
Author

ohld commented Mar 12, 2026

While we wait for the spec review, I created a facilitator for TON

https://github.com/ohld/x402-ton-facilitator

And our community also implemented one: https://github.com/TONresistor/x402-ton

We'll start implementing all SDKs as well.

ohld added a commit to ohld/x402 that referenced this pull request Mar 12, 2026
Add @x402/tvm (TypeScript) and x402[tvm] (Python) mechanism packages
implementing the exact payment scheme for TON blockchain.

Python (mechanisms/tvm/):
- Full gasless USDT payment flow via TONAPI relay
- Ed25519 signature verification for W5R1 wallets
- BoC parser for external messages, jetton transfers
- 6-rule payment verification (protocol, signature, intent, replay,
  relay safety, simulation)
- Idempotent settlement with state machine
- 72 unit tests

TypeScript (@x402/tvm):
- SchemeNetworkClient/Server/Facilitator implementations
- W5R1 wallet signing with @ton/ton SDK
- Gasless estimate + settlement via TONAPI
- CAIP-2 network IDs: tvm:-239 (mainnet), tvm:-3 (testnet)
- 48 unit tests

Refs: spec PR x402-foundation#1455, live facilitator at ton-facilitator.okhlopkov.com
@TONresistor
Copy link
Copy Markdown

We implemented this spec in production with x402-ton (https://github.com/TONresistor/x402-ton).

Two findings from our integration:

Commission discovery at boot: The spec recommends discovering commission via /v2/gasless/estimate, but this endpoint requires a real unsigned BOC. A dummy/sample BOC always returns 400. We ended up using a config fallback (MAX_RELAY_COMMISSION) when the estimate is unavailable.

Native TON support: Our implementation supports both USDT gasless (internal auth via TONAPI) and native TON direct broadcast (external auth). The PR describes the jetton path only. We treat native TON as an additive superset since payers with TON already have gas and don't need the gasless relay.

Live facilitator: https://x402.resistance.dog
npm: x402ton@2.0.0

@phdargen phdargen self-assigned this Mar 16, 2026

- `payload.seqno` MUST match the wallet's current on-chain seqno.
- Duplicate `signedBoc` submissions MUST be rejected.
- The W5 message MUST contain exactly two actions: the payment `jetton_transfer` and (optionally) a relay commission `jetton_transfer`. Any additional actions MUST cause rejection.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- The W5 message MUST contain exactly two actions: the payment `jetton_transfer` and (optionally) a relay commission `jetton_transfer`. Any additional actions MUST cause rejection.
- The W5 message MUST contain at most two actions: the payment `jetton_transfer` and (optionally) a relay commission `jetton_transfer`. Any additional actions MUST cause rejection.

- `payTo`: Recipient TON address (raw format).
- `amount`: Atomic token amount (6 decimals for USDT, so `10000` = $0.01).
- `extra.relayAddress`: (Optional) Gasless relay address that receives the relay commission. When present, the W5 batch includes a separate `jetton_transfer` to this address as commission for gas sponsorship. When absent, the client handles gas fees directly.
- `extra.maxRelayCommission`: (Optional) Maximum relay commission in atomic token units. When present, facilitator MUST reject W5 batches where the commission transfer exceeds this amount. When absent, the facilitator applies its own commission policy.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who effectively pays the relayFee in the end? Is the client sending amount + relayFee or is the server receiving amount - relayFee?

- `asset`: [TEP-74] Jetton master contract address (raw format `workchain:hex`).
- `payTo`: Recipient TON address (raw format).
- `amount`: Atomic token amount (6 decimals for USDT, so `10000` = $0.01).
- `extra.relayAddress`: (Optional) Gasless relay address that receives the relay commission. When present, the W5 batch includes a separate `jetton_transfer` to this address as commission for gas sponsorship. When absent, the client handles gas fees directly.
Copy link
Copy Markdown
Collaborator

@phdargen phdargen Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please clarify if the facilitator acts as relayer or is the relayer an independent 3rd party actor?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If its a a 3rd party, how does the server know to which relay service the facilitator will submit the tx? Ie how to get relayAddress?


The `exact` scheme on TON transfers a specific amount of a [TEP-74] Jetton from the client to the resource server using a W5 wallet `internal_signed` message.

The facilitator sponsors gas by wrapping the client-signed message in an internal TON message. The client controls payment intent (asset, recipient, amount) through Ed25519 signature. The facilitator cannot modify the destination or amount.
Copy link
Copy Markdown
Collaborator

@phdargen phdargen Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From reading through the specs, it is my understanding that the client would sign two batched transfers to payTo and relayAddress. The facilitator wouldn't actually sponsor gas? The facilitator would effectively just pass through the client request to the relayer?

If this is the case, I think phrasing the relayer service as gas sponsorship is also a bit misleading. Its more like a mechanism for the client to pay for gas in a non-native token. But the client still effectively pays for the gas


1. Re-run all verification checks (do not trust prior `/verify` result).
2. Submit `signedBoc` via gasless relay or direct broadcast:
- **Sponsored (gasless):** `POST /v2/gasless/send` with `{ wallet_public_key, boc }` to a relay service. The relay wraps the signed message in an internal message carrying TON for gas.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like relayer is an independent service


- The facilitator/relay account MUST NOT appear as the source of the Jetton transfer.
- The facilitator MUST NOT be the payer (`walletAddress`) for the delegated transfer.
- The facilitator address MUST NOT appear as destination in any `jetton_transfer` within the W5 message (except for the relay commission transfer to `extra.relayAddress`).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like facilitator == relayer

### 4. Replay and anti-abuse checks

- `payload.seqno` MUST match the wallet's current on-chain seqno.
- Duplicate `signedBoc` submissions MUST be rejected.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be discussed in much more detail. Could you please elaborate why this is needed and how this would work in practice? Would this imply a stateful facilitator?

x402 is designed as a stateless protocol. We recently made one exception for SVM to patch a security vulnerability, see https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_svm.md#duplicate-settlement-mitigation-recommended.

If TON could use a similar approach as SVM with a short-term, in-memory cache of transaction payloads, that would be acceptable but should be well justified in a dedicated section of the spec.

If persistent storage would be needed, this would warrant a deeper discussion with the core team

@phdargen
Copy link
Copy Markdown
Collaborator

phdargen commented Mar 16, 2026

Hi @ohld, thanks again for your contribution! This a great start and the spec is well written and structured.

Please have a look at my comments above, there a 2 main points to clarify:

  1. relayer model, in particular clearly separating the roles of facilitator vs relayer (or explicitly stating that the facilitator is the relayer if thats the case)
  2. state requirement on the facilitator

@phdargen
Copy link
Copy Markdown
Collaborator

phdargen commented Mar 16, 2026

Would it be possible to let the facilitator actually sponsor the gas aligning the flow with the existing mechanism for other chains?

Concretely I would suggest the following (if technical possible) that would significantly simplify the spec:

  • Client signs ONLY one jetton_transfer to payTo
  • The facilitator appends the jetton_transfer to relayAddress and pays the gas (ie not the client)
  • Server and client would not need to know anything about the relayer like relayAddress or maxRelayCommission, all handled by the facilitator
  • The facilitator could either outsource the relay to an external service (as long as it pays for it) OR implement its own relay logic and charging the client no relay fee OR just submit the tx directly and paying gas in TON (as long as it pays for it). This could remain an implementation detail of the facilitator
  • The facilitator is compensated by the server out of band as for the other networks

@ohld
Copy link
Copy Markdown
Author

ohld commented Mar 16, 2026

Updated spec to self-relay architecture. Tested with 7 mainnet USDT payments.

Key change: Facilitator = relay. No commission. Client signs 1 jetton_transfer,
facilitator wraps + sponsors gas (~$0.04/tx).

Review comments:

  1. Gas sponsorship (line 22) — Facilitator pays ~0.013 TON/tx. Zero client cost.
  2. Verify required (line 36) — Fixed. Now REQUIRED.
    3-4. Facilitator vs relayer (lines 68-69) — One entity. relayAddress removed.
  3. Relay fee (line 69) — None. Client sends exact amount.
  4. Extra fields (line 71) — Removed assetDecimals/assetSymbol. Only facilitatorUrl remains.
  5. Transfer count (line 166) — Exactly 1. Enforced in verification rule 3.
  6. Statefulness (line 174) — In-memory BoC hash dedup, 600s TTL. Same as SVM pattern.
    9-13. Relay fee bounds, who pays — N/A, no relay fee exists.

cc @nicholasrice — you've been working on TON support too (#1495)

@ohld
Copy link
Copy Markdown
Author

ohld commented Mar 17, 2026

One thing worth flagging: TON gas is 0.013 TON ($0.04) per jetton transfer, which is 20-40x more than Base ($0.001) or Solana ($0.0005). This makes the "facilitator absorbs gas" model harder to sustain long-term compared to EVM/SVM.

For now my facilitator covers gas from personal funds, same as the free facilitators on other chains. But for production sustainability, facilitators would likely either bill merchants out-of-band (the standard x402 model) or include an optional commission transfer in the /prepare response — the client sees and signs everything, fully transparent. Per @phdargen's earlier guidance, relay economics are an implementation detail of the facilitator, so this stays outside the spec itself.

Worth noting: TON Foundation could sponsor a public facilitator to bootstrap adoption (similar to Coinbase's CDP facilitator on Base). And realistically, TON x402 makes more sense for $0.10+ payments where gas overhead is proportional.

None of this changes the spec — just providing context on operational economics.

ohld added 10 commits April 13, 2026 19:44
Changes:
- Remove /prepare endpoint: client resolves seqno + jetton wallet via
  TON RPC directly (same pattern as SVM/Stellar/Aptos)
- Remove nonce field: dedup uses BoC hash, not nonce
- Transfer amount: strict equality (== not >=)
- Add jetton wallet verification: recipient MUST match
  get_wallet_address(payTo) on the asset master
- Seqno check: MUST NOT be less than on-chain seqno (was SHOULD)
- Add client balance check requirement
- Move simulation to /verify (was optional pre-settlement)
- Remove HTTP header references (transport-agnostic)
- Replace hardcoded 600s with maxTimeoutSeconds
- Add Client RPC Requirements appendix section
- Add section 3 (Facilitator Safety): facilitator address MUST NOT
  appear as sender or Jetton transfer source. Matches SVM/Stellar.
- Make get_wallet_address check explicit per @skywardboundd: the
  destination in the deserialized BoC must match the resolved wallet.
- Align verification intro phrasing with SVM/Stellar style.
Follows Stellar's approach: extra.areFeesSponsored boolean, currently
always true. Non-sponsored flow (client pays gas) to be added later.
… check

- Remove duplicate validUntil check from section 2 (already in section 5)
- Add stateInit code hash verification for seqno==0 deployments
  (per arjun215-eng's earlier feedback)
Per @skywardboundd review:
1. payload.to MUST equal requirements.payTo (explicit metadata check)
2. Source Jetton wallet (W5 internal msg destination) MUST match
   get_wallet_address(from) — prevents substitute source contract
3. jetton_transfer body destination MUST equal requirements.payTo

Tested against deployed facilitator with real on-chain data.
Facilitator commit: ohld/x402-ton-facilitator@6f92320
… W5R1 hash

- Remove facilitatorUrl from extra (implementation detail, not protocol field)
- Fix dedup TTL: use fixed 300s instead of per-request maxTimeoutSeconds
- Add canonical W5R1 code hash for stateInit verification
- Note simulation as alternative to code hash checks for seqno>0

Per @phdargen review on coinbase/x402#1455
Major rework based on TON Core team review:
- settlementBoc is now an internal message (not external)
- Payload reduced to {settlementBoc, asset}, all fields derived from BoC
- Public key derived from stateInit or on-chain get_public_key
- Seqno check changed to strict equality (TON requirement)
- Internal message must be bounceable
- Removed facilitatorUrl, nonce, walletPublicKey
- Clarified simulation in /verify, extensibility for non-gasless flows
- Updated parent scheme_exact.md TON section
…tion, simulation chain

- Replace seqno==0 with proper TON account states (nonexist/uninit) for stateInit condition
- Clarify that payload.asset is a JSON field, not a BoC-internal field (TEP-74 has no asset)
- Add full TEP-74 transfer chain verification to simulation section
- Remove non-normative timing note from settlement step 7
- Add W5 gasless transactions reference link
- Narrow wallet scope to W5 v5r1 (not v5r1+), only version that exists today
- Remove redundant "x402 protocol-level requirement" sentence
- Add get_jetton_data hint for decimal resolution
- Add walletId to derived fields list
- Rename section 2 to "Message and signature verification"
- Move balance check from Replay protection to Payment intent
- Remove redundant seqno explanation sentence
@ohld ohld force-pushed the feat/scheme-exact-ton branch from 2dac972 to 7af4477 Compare April 13, 2026 12:44
@github-actions github-actions bot removed ecosystem Additions to ecosystem site typescript go sdk Changes to core v2 packages examples Changes to examples python legacy Changes to legacy sdk or examples ci website docs labels Apr 13, 2026
@ohld ohld requested a review from phdargen April 13, 2026 13:01
Refine the TON `exact` scheme spec to better describe how client-signed W5 jetton transfers are verified and relayed by the facilitator.

This update expands the TON payment requirements and verification rules to account for optional jetton transfer parameters exposed through `extra`, and clarifies how those parameters are interpreted when they are omitted.

Changes included:
- add optional `extra.forwardPayload` and `extra.forwardTonAmount` to TON
  `PaymentRequirements` and define their effective defaults as zero-bit payload and `"0"`
- add optional `extra.responseDestination` and define its effective default as `addr_none`
- require facilitators to compare the effective values of `responseDestination`, `forwardPayload`, and `forwardTonAmount` between `accepted.extra` and `requirements.extra`
- extend payment intent validation so the facilitator derives and checks `response_destination`, `forward_payload`, and `forward_ton_amount` from the signed W5 action
- clarify that the facilitator-funded relay must cover both outer relay execution and the value expected by the client-signed inner transfer
- clarify that the client-signed inner message must carry enough TON to fund payer-side execution from the payer jetton wallet to the source jetton wallet
- specify that the outbound internal message from the client W5 wallet to the source jetton wallet must be bounceable
- specify that the `settlementBoc` wrapper itself does not need to be bounceable
- clean up terminology and consistency across the document: `BoC`, `zero-bit`, `forwardPayload`, `forwardTonAmount`, and derived field descriptions

This is a spec clarification/update only. It does not introduce a new TON payment scheme, but makes the existing gas-sponsored W5 relay flow more precise.
@ArkadiyStena ArkadiyStena force-pushed the feat/scheme-exact-ton branch from 7af4477 to 69fca0a Compare April 13, 2026 13:24
@Alejandbel
Copy link
Copy Markdown

Hey, @phdargen!

We have updated our commits to be verified

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

specs Spec changes or additions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants